/*----------------------------------------------------------------------------
 * Copyright (c) <2013-2018>, <Huawei Technologies Co., Ltd>
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 * conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 * of conditions and the following disclaimer in the documentation and/or other materials
 * provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 * to endorse or promote products derived from this software without specific prior written
 * permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *---------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------
 * Notice of Export Control Law
 * ===============================================
 * Huawei LiteOS may be subject to applicable export control laws and regulations, which might
 * include those applicable to Huawei LiteOS of U.S. and the country in which you are located.
 * Import, export and usage of Huawei LiteOS in any manner by you shall be in compliance with such
 * applicable export control laws and regulations.
 *---------------------------------------------------------------------------*/

#include "linux/workqueue.h"
#include "los_swtmr_pri.h"
#include "los_event.h"

struct workqueue_struct *g_pstSystemWq = (struct workqueue_struct *)NULL;
pthread_mutex_t wq_mutex = PTHREAD_MUTEX_INITIALIZER;

static cpu_workqueue_struct *init_cpu_workqueue(struct workqueue_struct *wq, int cpu);
static int create_workqueue_thread(cpu_workqueue_struct *cwq, int cpu);
static void worker_thread(cpu_workqueue_struct *__cwq);
static void run_workqueue(cpu_workqueue_struct *cwq);

/*
 * @ingroup workqueue
 * Obtain the first work in a workqueue.
 *
 */
#define worklist_entry(ptr, type, member)  ((type *)((char *)(ptr)-((AARCHPTR)&(((type*)0)->member))))

/*
 * @ingroup workqueue
 * Traverse a workqueue.
 *
 */
#define LIST_FOR_WORK(pos, list_object, type, field)      \
        for (pos = LOS_DL_LIST_ENTRY((list_object)->next, type, field);     \
             &pos->field != (list_object);                            \
             pos = LOS_DL_LIST_ENTRY(pos->field.next, type, field))

#define LIST_FOR_WORK_DEL(pos, pstnext, list_object, type, field)      \
                for (pos = LOS_DL_LIST_ENTRY((list_object)->next, type, field),     \
                        pstnext = LOS_DL_LIST_ENTRY(pos->field.next, type, field);     \
                     &pos->field != (list_object);                            \
                     pos = pstnext, pstnext = LOS_DL_LIST_ENTRY(pos->field.next, type, field))

void delayed_work_timer_fn(unsigned long __data);
void init_delayed_work(struct delayed_work *_work, work_func_t _func)
{
    if (NULL == _work || NULL == _func)
    {
        return;
    }
    INIT_WORK((&((_work)->work)), _func);
    (_work)->timer.function = delayed_work_timer_fn;
    (_work)->timer.data = (unsigned long)(_work);
    (_work)->timer.flag = TIMER_UNVALID;
    (_work)->work.work_status = 0;
}

static unsigned int wq_is_empty(cpu_workqueue_struct *cwq)
{
    UINTPTR uwIntSave;
    UINT32 uwRet;

    uwIntSave = LOS_IntLock();
    uwRet = list_empty(&cwq->worklist);
    LOS_IntRestore(uwIntSave);

    return uwRet;
}

struct workqueue_struct *__create_workqueue_key(char *name,
                        int swSinglethread,
                        int swFreezeable,
                        int swRt,
                        struct lock_class_key *pstKey,
                        const char *pLock_name)
{
    struct workqueue_struct *pstWq = (struct workqueue_struct *)NULL;
    cpu_workqueue_struct *pstCwq   = (cpu_workqueue_struct *)NULL;
    UINT32 uwRet;

    if (NULL == name)
    {
        return (struct workqueue_struct *)NULL;
    }

    pstWq = (struct workqueue_struct *)LOS_MemAlloc(m_aucSysMem0, sizeof(struct workqueue_struct));
    if (NULL == pstWq)
    {
        return (struct workqueue_struct *)NULL;
    }

    pstWq->cpu_wq = (cpu_workqueue_struct *)LOS_MemAlloc(m_aucSysMem0, sizeof(cpu_workqueue_struct));
    if (NULL == pstWq->cpu_wq)
    {
        (VOID)LOS_MemFree(m_aucSysMem0, (VOID *)pstWq);
        return (struct workqueue_struct *)NULL;
    }

    pstWq->name = name;
    pstWq->singlethread = swSinglethread;
    pstWq->freezeable = swFreezeable;
    pstWq->rt = swRt;
    pstWq->delayed_work_count =0;
    INIT_LIST_HEAD(&pstWq->list);
    (VOID)LOS_EventInit(&pstWq->wq_event);

    if (swSinglethread)
    {
        pstCwq = init_cpu_workqueue(pstWq, swSinglethread);
        uwRet = create_workqueue_thread(pstCwq, swSinglethread);
    }
    else
    {
        return (struct workqueue_struct *)NULL;
    }

    if (uwRet)
    {
        destroy_workqueue(pstWq);
        pstWq = (struct workqueue_struct *)NULL;
    }

    return pstWq;
}

struct workqueue_struct * create_singlethread_workqueue(char *name)
{
    return __create_workqueue_key(name,1,0,0,(struct lock_class_key *)NULL, (const char *)NULL);
}

static cpu_workqueue_struct *init_cpu_workqueue(struct workqueue_struct *pstWq, int cpu)
{
    cpu_workqueue_struct *cwq = pstWq->cpu_wq;

    cwq->wq = pstWq;
    INIT_LIST_HEAD(&cwq->worklist);

    return cwq;
}

static int create_workqueue_thread(cpu_workqueue_struct *cwq, int cpu)
{
    struct workqueue_struct *wq = cwq->wq;
    TSK_INIT_PARAM_S os_task_init_param;
    UINT32 uwRet;

    os_task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)worker_thread;
    os_task_init_param.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;
    os_task_init_param.pcName       = wq->name;
    os_task_init_param.usTaskPrio   = 1;
    os_task_init_param.auwArgs[0] = (AARCHPTR)cwq;
    os_task_init_param.uwResved   = LOS_TASK_STATUS_DETACHED;

    uwRet = LOS_TaskCreate(&cwq->wq->wq_id, &os_task_init_param);
    if(LOS_OK != uwRet)
    {
        return LOS_NOK;
    }

    cwq->thread = (task_struct*)OS_TCB_FROM_TID(cwq->wq->wq_id);
    (VOID)LOS_TaskYield();

    return LOS_OK;
}

static void worker_thread(cpu_workqueue_struct *__cwq)
{
    cpu_workqueue_struct *cwq = __cwq;

    for (;;)
    {
        if (wq_is_empty(cwq))
        {
            (VOID)LOS_EventRead(&(cwq->wq->wq_event), 0x01, LOS_WAITMODE_OR |
                LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);
        }
        run_workqueue(cwq);
    }
}

static void run_workqueue(cpu_workqueue_struct *pstCwq)
{
    struct work_struct *work = (struct work_struct *)NULL;
    UINTPTR uwIntSave;
    work_func_t func = NULL;

    if (!wq_is_empty(pstCwq))
    {
        uwIntSave = LOS_IntLock();
        work = worklist_entry(pstCwq->worklist.next, struct work_struct, entry);/*lint !e413*/
        work->work_status |= WORK_STRUCT_RUNNING;
        list_del_init(pstCwq->worklist.next);
        func = work->func;
        LOS_IntRestore(uwIntSave);

        pstCwq->current_work = work;
        func(work);
        pstCwq->current_work = (struct work_struct *)NULL;

        if (work->work_status & WORK_STRUCT_RUNNING)
        {
            work->work_status &= ~(WORK_STRUCT_RUNNING | WORK_STRUCT_PENDING);
        }
    }
}

static void __list_add(struct list_head * _new, struct list_head * prev, struct list_head * next)
{
    next->prev = _new;
    _new->next = next;
    _new->prev = prev;
    prev->next = _new;
}
#ifdef WORKQUEUE_SUPPORT_PRIORITY
static void worklist_add(struct list_head *_new, struct list_head *head, unsigned int work_pri)
{
    struct work_struct *work = (struct work_struct *)NULL;
    struct list_head   *list = head;
    do{
        list = list->next;
        if (list == head)
        {
            break;
        }
        work = worklist_entry(list, struct work_struct, entry);/*lint !e413*/
    }while(work->work_pri <= work_pri);

    __list_add(_new, list->prev, list);
}
#else
static void worklist_add_tail(struct list_head *_new, struct list_head *head)
{
    __list_add(_new, head->prev, head);
}
#endif

static void insert_work(cpu_workqueue_struct *cwq, struct work_struct *work, struct list_head *head)
{
#ifdef WORKQUEUE_SUPPORT_PRIORITY
    worklist_add(&work->entry, head, work->work_pri);
#else
    worklist_add_tail(&work->entry, head);
#endif
    (VOID)LOS_EventWrite(&(cwq->wq->wq_event), 0x01);
}

static void __queue_work(cpu_workqueue_struct *pstCwq, struct work_struct *work)
{
    insert_work(pstCwq, work, &pstCwq->worklist);
}

bool queue_work_on(struct workqueue_struct *wq, struct work_struct *work)
{
    bool uwRet = FALSE;
    struct work_struct *pstwork = (struct work_struct *)NULL;

    if (wq_is_empty(wq->cpu_wq))
    {
        uwRet = TRUE;
    }
    else
    {
        LIST_FOR_WORK(pstwork, &wq->cpu_wq->worklist, struct work_struct, entry)/*lint !e413*/
        {
            if (pstwork == work)
            {
                return FALSE;
            }
        }
        uwRet = TRUE;
    }
    __queue_work(wq->cpu_wq, work);

    return uwRet;
}

bool queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
    bool uwRet = FALSE;
    UINTPTR uwIntSave;

    if (NULL == wq || NULL == wq->name || NULL == work)
    {
        return FALSE;
    }

    uwIntSave = LOS_IntLock();

    work->work_status = WORK_STRUCT_PENDING;
    uwRet = queue_work_on(wq, work);

    LOS_IntRestore(uwIntSave);
    return uwRet;
}

bool cancel_work_sync(struct work_struct *work)
{
    bool uwRet = FALSE;

    if (NULL == work)
    {
        return FALSE;
    }

    if (!work->work_status)
    {
        uwRet = FALSE;
    }
    else if (work->work_status & WORK_STRUCT_RUNNING)
    {
        uwRet = FALSE;
    }
    else
    {
        uwRet = TRUE;
    }
    while (work->work_status)
    {
        (void)usleep(10000);
    }
    return uwRet;
}

bool flush_work(struct work_struct *work)
{
    if (NULL == work)
    {
        return FALSE;
    }

    if (work->work_status & WORK_STRUCT_PENDING)
    {
        while (work->work_status)
        {
            (void)usleep(10000);
        }
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

void delayed_work_timer_fn(unsigned long data)
{
    struct delayed_work *dwork = (struct delayed_work *)data;
    UINTPTR uwIntSave;

    /* should have been called from irqsafe timer with irq already off */
    uwIntSave = LOS_IntLock();
    dwork->wq->delayed_work_count--;
    (void)queue_work_on(dwork->wq, &dwork->work);
    (void)del_timer(&dwork->timer);
    LOS_IntRestore(uwIntSave);
}

bool queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dwork, unsigned int delaytime)
{
    bool  uwRet = TRUE;
    UINTPTR uwIntSave;
    UINT32 i;
    struct work_struct *pstwork  = (struct work_struct *)NULL;
    SWTMR_CTRL_S *pstCurSwTmr    = (SWTMR_CTRL_S *)NULL;
    LOS_DL_LIST *pstListObject   = (LOS_DL_LIST *)NULL;
    SortLinkList *pstListSorted = (SortLinkList *)NULL;

    if ((NULL == wq) || (NULL == wq->name) || (NULL == wq->cpu_wq) || (NULL == dwork))
    {
        return FALSE;
    }

    dwork->wq = wq;
    if (delaytime == 0)
    {
        uwRet = queue_work(dwork->wq, &dwork->work);
        return uwRet;
    }
    else
    {
        uwIntSave = LOS_IntLock();
        for (i = 0; i < OS_TSK_SORTLINK_LEN; i++)
        {
            pstListObject = g_swtmrSortLink.sortLink + i;
            if (!LOS_ListEmpty(pstListObject))
            {
                pstListSorted = LOS_DL_LIST_ENTRY((pstListObject)->pstNext, SortLinkList, sortLinkNode); /*lint !e413*/
                do
                {
                    pstCurSwTmr = LOS_DL_LIST_ENTRY(pstListSorted, SWTMR_CTRL_S, stSortList);
                    if (((struct delayed_work *)pstCurSwTmr->uwArg) == dwork)
                    {
                        LOS_IntRestore(uwIntSave);
                        return FALSE;
                    }
                    pstListSorted = LOS_DL_LIST_ENTRY(pstListSorted->sortLinkNode.pstNext, SortLinkList, sortLinkNode); /*lint !e413*/
                } while (&pstListSorted->sortLinkNode != (pstListObject));
            }
        }

        if (wq_is_empty(wq->cpu_wq))
        {
            uwRet = TRUE;
        }
        else
        {
            LIST_FOR_WORK(pstwork, &wq->cpu_wq->worklist, struct work_struct, entry)/*lint !e413*/
            {
                if(pstwork == &dwork->work)
                {
                    LOS_IntRestore(uwIntSave);
                    return FALSE;
                }
            }
        }
    }

    dwork->timer.expires = delaytime;
    add_timer(&dwork->timer);
    wq->delayed_work_count++;
    dwork->work.work_status = WORK_STRUCT_PENDING;

    LOS_IntRestore(uwIntSave);
    return uwRet;
}

bool cancel_delayed_work(struct delayed_work *dwork)
{
    SWTMR_CTRL_S       *pstCurSwTmr = (SWTMR_CTRL_S *)NULL;
    struct work_struct *pstwork     = (struct work_struct *)NULL;
    struct work_struct *pstworknext = (struct work_struct *)NULL;
    UINTPTR uwIntSave;
    bool  uwRet = FALSE;
    LOS_DL_LIST   *pstListObject = (LOS_DL_LIST *)NULL;
    SortLinkList *pstListSorted = (SortLinkList *)NULL;
    UINT32 i;

    if ((NULL == dwork) || (NULL == dwork->wq))
    {
        return FALSE;
    }

    uwIntSave = LOS_IntLock();
    if (dwork->work.work_status & WORK_STRUCT_PENDING)
    {

        for (i = 0; i < OS_TSK_SORTLINK_LEN; i++)
        {
            pstListObject = g_swtmrSortLink.sortLink + i;
            if (!LOS_ListEmpty(pstListObject))
            {
                pstListSorted = LOS_DL_LIST_ENTRY((pstListObject)->pstNext, SortLinkList, sortLinkNode); /*lint !e413*/
                do
                {
                    pstCurSwTmr = LOS_DL_LIST_ENTRY(pstListSorted, SWTMR_CTRL_S, stSortList);
                    if (pstCurSwTmr->usTimerID == dwork->timer.timerid && pstCurSwTmr->ucState == OS_SWTMR_STATUS_TICKING)
                    {
                        (void)del_timer(&dwork->timer);
                        dwork->work.work_status = 0;
                        dwork->wq->delayed_work_count--;
                        LOS_IntRestore(uwIntSave);
                        return TRUE;
                    }

                    pstListSorted = LOS_DL_LIST_ENTRY(pstListSorted->sortLinkNode.pstNext, SortLinkList, sortLinkNode); /*lint !e413*/
                } while (&pstListSorted->sortLinkNode != (pstListObject));
            }
        }

        if (dwork->work.work_status & WORK_STRUCT_RUNNING)
        {
            uwRet = FALSE;
        }
        else if (dwork->work.work_status & WORK_STRUCT_PENDING)
        {
            LIST_FOR_WORK_DEL(pstwork, pstworknext, &dwork->wq->cpu_wq->worklist, struct work_struct, entry)/*lint !e413*/
            {
                if (pstwork == &dwork->work)
                {
                    list_del_init(&pstwork->entry);
                    dwork->work.work_status = 0;
                    uwRet = TRUE;
                    break;
                }
            }
        }
        LOS_IntRestore(uwIntSave);
        return uwRet;
    }
    else
    {
        LOS_IntRestore(uwIntSave);
        return FALSE;
    }
}

bool cancel_delayed_work_sync(struct delayed_work *dwork)
{
    bool  uwRet = FALSE;

    if (NULL == dwork)
    {
        return FALSE;
    }

    LOS_TaskLock();

    uwRet = cancel_delayed_work(dwork);

    LOS_TaskUnlock();
    return uwRet;
}

bool flush_delayed_work(struct delayed_work *dwork)
{
    bool  uwRet = FALSE;
    SWTMR_CTRL_S *pstCurSwTmr    = (SWTMR_CTRL_S *)NULL;
    LOS_DL_LIST *pstListObject   = (LOS_DL_LIST *)NULL;
    SortLinkList *pstListSorted = (SortLinkList *)NULL;
    UINTPTR uwIntSave;
    UINT32 i;

    if ((NULL == dwork) || (NULL == dwork->wq))
    {
        return FALSE;
    }

    uwIntSave = LOS_IntLock();
    if (dwork->work.work_status & WORK_STRUCT_PENDING)
    {
        for (i = 0; i < OS_TSK_SORTLINK_LEN; i++)
        {
            pstListObject = g_swtmrSortLink.sortLink + i;
            if (!LOS_ListEmpty(pstListObject))
            {
                pstListSorted = LOS_DL_LIST_ENTRY((pstListObject)->pstNext, SortLinkList, sortLinkNode); /*lint !e413*/
                do
                {
                    pstCurSwTmr = LOS_DL_LIST_ENTRY(pstListSorted, SWTMR_CTRL_S, stSortList);
                    if (pstCurSwTmr->usTimerID == dwork->timer.timerid && pstCurSwTmr->ucState == OS_SWTMR_STATUS_TICKING)
                    {
                        (void)del_timer(&dwork->timer);
                        dwork->wq->delayed_work_count--;
                        (void)queue_work(dwork->wq, &dwork->work);
                        break;
                    }

                    pstListSorted = LOS_DL_LIST_ENTRY(pstListSorted->sortLinkNode.pstNext, SortLinkList, sortLinkNode); /*lint !e413*/
                } while (&pstListSorted->sortLinkNode != (pstListObject));
            }
        }

        (void)flush_work(&dwork->work);
        uwRet = TRUE;
    }
    else
    {
        uwRet = FALSE;
    }

    LOS_IntRestore(uwIntSave);
    return uwRet;
}

unsigned int work_busy(struct work_struct *work)
{
    unsigned int uwRet = 0;

    if (NULL == work)
        return FALSE;

    if (work->work_status & WORK_STRUCT_PENDING)
    {
        uwRet |= WORK_BUSY_PENDING;
    }
    if (work->work_status & WORK_STRUCT_RUNNING)
    {
        uwRet |= WORK_BUSY_RUNNING;
    }
    return uwRet;
}

bool schedule_work(struct work_struct *work)
{
    bool  uwRet = FALSE;
    UINTPTR uwIntSave;

    if (NULL == g_pstSystemWq || NULL == g_pstSystemWq->name || NULL == work)
    {
        return FALSE;
    }

    uwIntSave = LOS_IntLock();

    work->work_status = WORK_STRUCT_PENDING;
    uwRet = queue_work_on(g_pstSystemWq, work);

    LOS_IntRestore(uwIntSave);
    return uwRet;
}

bool schedule_delayed_work(struct delayed_work *dwork, unsigned int delaytime)
{
    bool  uwRet = FALSE;
    UINTPTR uwIntSave;

    if (NULL == g_pstSystemWq || NULL == dwork)
    {
        return FALSE;
    }

    uwIntSave = LOS_IntLock();

    uwRet = queue_delayed_work(g_pstSystemWq, dwork, delaytime);

    LOS_IntRestore(uwIntSave);
    return uwRet;
}

void drain_workqueue(struct workqueue_struct *wq)
{
    while (1)
    {
        (void)usleep(10000);
        if (wq_is_empty(wq->cpu_wq) && wq->delayed_work_count == 0)
        {
            break;
        }
    }
}

void destroy_workqueue(struct workqueue_struct *pstWq)
{
    if (NULL == pstWq)
    {
        return;
    }

    /* drain it before proceeding with destruction */
    drain_workqueue(pstWq);

    (VOID)pthread_mutex_lock(&wq_mutex);
    pstWq->name = (char *)NULL;
    list_del_init(&pstWq->list);
    (VOID)LOS_TaskDelete(pstWq->wq_id);
    (VOID)LOS_EventDestroy(&(pstWq->wq_event));

    (VOID)LOS_MemFree(m_aucSysMem0, (VOID *)(pstWq->cpu_wq));
    (VOID)LOS_MemFree(m_aucSysMem0, (VOID *)(pstWq));
    (VOID)pthread_mutex_unlock(&wq_mutex);
}
